// This file is part of OpenTSDB.
// Copyright (C) 2017 TeleDB Development Group.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at your
// option) any later version.  This program is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.  You should have received a copy
// of the GNU Lesser General Public License along with this program.  If not,
// see <http://www.gnu.org/licenses/>.
package net.opentsdb.utils.expir;

import net.opentsdb.utils.expiringmap.ExpirationPolicy;
import net.opentsdb.utils.expiringmap.ExpiringMap;
import org.hbase.async.jsr166e.LongAdder;
import org.junit.Assert;
import org.junit.Test;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class TestExpiringMap {

    private final static ByteBuffer key = ByteBuffer.wrap("key".getBytes());
    private final static ByteBuffer key_batch = ByteBuffer.wrap("key_batch".getBytes());

    /**
     * 检查过期是否有效
     *
     * @throws Exception
     */
    @Test
    public void testExpiringMapStart() throws Exception{
        ExpiringMap<ByteBuffer,LongAdder> map = ExpiringMap
                .builder()
                .variableExpiration()
                .ttl(2,TimeUnit.SECONDS)
                .build();
        LongAdder value = new LongAdder();
        value.add(1);
        map.put(key, value);

        Assert.assertTrue(map.containsKey(key));

        Thread.sleep(2*1000+100);

        Assert.assertFalse(map.containsKey(key));
    }


    /**
     * 重新修改失效时间单元测试
     *
     * @throws Exception
     */
    @Test
    public void testExpiringResetExipreTime() throws Exception{
        ExpiringMap<ByteBuffer,LongAdder> map = ExpiringMap
                .builder()
                .ttl(2,TimeUnit.SECONDS)
                .variableExpiration()
                .build();
        LongAdder value = new LongAdder();
        value.add(1);
        map.put(key, value);

        Assert.assertTrue(map.containsKey(key));

        Thread.sleep(1*1000);
        // 取消过期
        map.setTimestramp(key,System.currentTimeMillis()+4*1000,TimeUnit.MILLISECONDS);
        Thread.sleep(2*1000);
        Assert.assertTrue(map.containsKey(key));

        // 针对key设置重新过期
        map.setTtl(3, TimeUnit.SECONDS);
        Thread.sleep(4*1000+100);
        Assert.assertFalse(map.containsKey(key));
    }


    /**
     * 测试移除触发监听器
     *
     * @throws Exception
     */
    @Test
    public void testListener() throws Exception{
        ExpiringMap<ByteBuffer,LongAdder> map = ExpiringMap
                .builder()
                .ttl(2,TimeUnit.SECONDS)
                .variableExpiration()
                .build();
        // 同步触发
        map.addExpirationListener((k,v) -> {
            System.out.println("remove key:"+StandardCharsets.UTF_8.decode(k).toString()+
                    " remove value:"+v.sum());
        });

        LongAdder value = new LongAdder();
        value.add(1);
        map.put(key, value);

        Assert.assertTrue(map.containsKey(key));
        Thread.sleep(2*1000);
        Assert.assertNull("assert pass expire time",map.get(key));
        Assert.assertFalse(map.containsKey(key));
    }

    /**
     * 模拟实现跑数据流程
     *
     * @throws Exception
     */
    @Test
    public void testExecute() throws Exception{
        final int MAX_SIZE = 15;
        final long DEFAULT_TTL = 5;

        ExpiringMap<ByteBuffer,LongAdder> map = ExpiringMap
                .builder()
                .maxSize(MAX_SIZE)
                .ttl(DEFAULT_TTL,TimeUnit.SECONDS)
                .variableExpiration()
                .expirationPolicy(ExpirationPolicy.ACCESSED)
                .build();

        map.addExpirationListener((k,v) -> {
            System.out.println("remove key:"+StandardCharsets.UTF_8.decode(k).toString()+
                    " remove value:"+v.sum());
        });

        // 初始化
        LongAdder defaultAdder;
        for(int i=0; i<15; i++){
            defaultAdder = new LongAdder();
            defaultAdder.add(1);
            map.put(ByteBuffer.wrap(("key_"+i).getBytes()), defaultAdder);
        }

        Assert.assertTrue(map.size() == MAX_SIZE);
        System.out.println("1."+map.size());

        Thread.sleep(2*1000);

        for(int i=10; i<15; i++){
            map.setTimestramp(ByteBuffer.wrap(("key_"+i).getBytes()));
        }

        for(int i=0; i<15; i++){
            System.out.println("key_"+i+":"+map.getTimestramp(ByteBuffer.wrap(("key_"+i).getBytes())));
        }

        Thread.sleep(3*1000+100);

        Assert.assertTrue(map.size() == 5);

//        System.out.println("2.  "+map.size()+
//                " now:"+System.currentTimeMillis()+
//                " "+map.getExpiration());
//
//        map.setExpiration(8,TimeUnit.SECONDS);
//        System.out.println("2.  "+map.size()+
//                " now:"+System.currentTimeMillis()+
//                " "+map.getExpiration());

        Thread.sleep(1*1000);
        map.setTtl(3,TimeUnit.SECONDS);
        map.setTimestramp(ByteBuffer.wrap(("key_0").getBytes()),3,TimeUnit.SECONDS);
        System.out.println("2."+map.size());
        Thread.sleep(1*1000);

//        Assert.assertTrue(map.size() == 5);
        System.out.println("3."+map.size());

        Thread.sleep(1*1000);

//        Assert.assertTrue(map.size() == 0);
        System.out.println("4."+map.size());




    }

    /**
     * 模拟实现跑数据流程
     *      测试修改ttl和访问模式是否生效
     *
     * @throws Exception
     */
    @Test
    public void testStringExecute() throws Exception{
        final int MAX_SIZE = 15;
        final long DEFAULT_TTL = 5;

        ExpiringMap<String,LongAdder> map = ExpiringMap
                .builder()
                .maxSize(MAX_SIZE)
                .ttl(DEFAULT_TTL,TimeUnit.SECONDS)
                .expirationPolicy(ExpirationPolicy.ACCESSED)
                .build();

        map.addExpirationListener((k,v) -> {
            System.out.println("remove key:"+k+
                    " remove value:"+v.sum());
        });

        // 初始化
        LongAdder defaultAdder;
        for(int i=0; i<15; i++){
            defaultAdder = new LongAdder();
            defaultAdder.add(1);
            map.put("key_"+i, defaultAdder);
        }

        Assert.assertTrue(map.size() == MAX_SIZE);
        Thread.sleep(4*1000);

        for(int i=0; i<5; i++){
            map.setTimestramp("key_"+i);
        }

        Thread.sleep(1*1000);
        Assert.assertTrue(map.size() == 5);

        Thread.sleep(1*1000);
        map.setTtl(6,TimeUnit.SECONDS);
        Thread.sleep(1*1000);
        Assert.assertTrue(map.size() == 5);

        Thread.sleep(3*1000);
        Assert.assertTrue(map.size() == 0);
    }

    @Test
    public void testSetTtl() throws Exception{
        final int MAX_SIZE = 15;
        final long DEFAULT_TTL = 10;

        ExpiringMap<String,LongAdder> map = ExpiringMap
                .builder()
                .maxSize(MAX_SIZE)
                .ttl(DEFAULT_TTL,TimeUnit.SECONDS)
                .expirationPolicy(ExpirationPolicy.ACCESSED)
                .build();

        map.addExpirationListener((k,v) -> {
            System.out.println(" current time "+ getCurrent() +" remove key:"+k+
                    " remove value:"+v.sum());
        });

        LongAdder defaultAdder;
        for(int i=0; i<2; i++){
            defaultAdder = new LongAdder();
            defaultAdder.add(1);
            map.put("key_"+i, defaultAdder);
        }

        Thread.sleep(1*1000);
        printSize(map.size());
//        Assert.assertTrue(map.size() == 2);

        map.setTtl(5,TimeUnit.SECONDS);

        Thread.sleep(4*1000);
        printSize(map.size());
//        Assert.assertTrue(map.size() == 0);

        Thread.sleep(5*1000);
        printSize(map.size());
//        Assert.assertTrue(map.size() == 0);



    }

    @Test
    public void testTime(){
        System.out.println(System.nanoTime());
    }

    public void printSize(int size){
        System.out.println("current time "+getCurrent()+" map size :"+size);
    }

    public String getCurrent(){
        long timeMillis = System.currentTimeMillis();

        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(timeMillis);

        String time = c.get(Calendar.HOUR_OF_DAY)+":"
                +c.get(Calendar.MINUTE)+":"
                +c.get(Calendar.SECOND)+"."
                +c.get(Calendar.MILLISECOND);

        return time;
    }
}
